package fr.pud.utils;

import java.io.File;
import java.io.IOException;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * This class provides a simple dom parser for xml file with the xpath option.
 * @author Pierre RELIQUET
 */
public class DOMParser {
    public static final boolean                 VALIDATOR                     = true;
    /*
     * Standard variables for generating and reading XML files
     */
    /*
     * Initialization of the factory
     */
    private static final DocumentBuilderFactory BUILDER_FACTORY               = DocumentBuilderFactory
                                                                                      .newInstance();
    private static final DocumentBuilderFactory BUILDER_FACTORY_VALIDATOR     = DocumentBuilderFactory
                                                                                      .newInstance();
    private static final XPathFactory           XPATH_FACTORY                 = XPathFactory
                                                                                      .newInstance();
    private static final XPath                  XPATH                         = XPATH_FACTORY
                                                                                      .newXPath();
    private static DocumentBuilder              builder                       = null;
    private static DocumentBuilder              builderValidatorWithoutBlanks = null;
    /*
     * Initialization of the document builders
     */
    static {
        BUILDER_FACTORY_VALIDATOR.setValidating(true);
        BUILDER_FACTORY_VALIDATOR.setIgnoringElementContentWhitespace(true);
        try {
            if (builder == null) {
                builder = BUILDER_FACTORY.newDocumentBuilder();
            }
            if (builderValidatorWithoutBlanks == null) {
                builderValidatorWithoutBlanks = BUILDER_FACTORY_VALIDATOR
                        .newDocumentBuilder();
            }
        }
        catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
    }
    private static final Document               DEFAULT_EMPTY_DOC             = builder
                                                                                      .newDocument(); ;

    /**
     * Count the elements which are tagged with tagName
     * @param d
     *            Document, the document to parse
     * @param tagName
     *            , the name of the tag
     * @param content
     *            , the content researched
     * @return the number of elements tagged with tagName
     */
    public static int countElementWithContent(Document d, String tagName,
            String content) {
        NodeList nodes = d.getElementsByTagName(tagName);
        int count = 0;
        for (int i = 0; i < nodes.getLength(); i++) {
            if (nodes.item(i).getTextContent().equals(content)) {
                count++;
            }
        }
        return count;
    }

    /**
     * @return an empty document which could be used to create new element
     */
    public static Document getDefaultEmptyDoc() {
        return DEFAULT_EMPTY_DOC;
    }

    /*
     * End of standard variables
     */
    /**
     * @return a standard document builder which can be used for parsing XML
     *         files into DOM tree
     */
    public static DocumentBuilder getDocumentBuilder() {
        return builder;
    }

    /**
     * @return the new builder which validates the DTD
     */
    public static DocumentBuilder getDocumentBuilderValidatorWithoutBlanks() {
        return builderValidatorWithoutBlanks;
    }

    /**
     * Method which provides the document source to serialize it
     * @param d
     *            , the document
     * @return the Source of d
     */
    public static Source getDOMSource(Document d) {
        return new DOMSource(d);
    }

    /**
     * @param path
     *            , the path of the file
     * @return the Document (== DOM tree)
     */
    public static Document getDOMTree(String path, boolean validation) {
        Document document = null;
        try {
            if (validation != VALIDATOR) {
                document = getDocumentBuilder().parse(
                        new org.xml.sax.InputSource(path));
            }
            else {
                document = getDocumentBuilderValidatorWithoutBlanks().parse(
                        new org.xml.sax.InputSource(path));
            }
        }
        catch (SAXException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return document;
    }

    /**
     * @param d
     *            , the document where the element is going to be created
     * @param name
     *            , the name of the element
     * @param text
     *            , the value of the text content
     * @return the new element
     */
    public static Element getElement(Document d, String name, String text) {
        Element e = d.createElement(name);
        e.setTextContent(text);
        return e;
    }

    /**
     * @param d
     *            , the document where the element is going to be created
     * @param name
     *            , the name of the element
     * @param text
     *            , the value of the text content
     * @param parent
     *            , the parent where the new element is going to be appended
     */
    public static void getElementAppended(Document d, String name, String text,
            Element parent) {
        parent.appendChild(getElement(d, name, text));
    }

    /**
     * @return an empty document
     */
    public static Document getEmptyDOMTree() {
        return getDocumentBuilder().newDocument();
    }

    /**
     * Prepare the xpath expression to avoid copy paste
     * @param document
     *            , the document to parse
     * @param query
     *            , the xpath query
     * @return the query with an XPathExpression format
     */
    private static XPathExpression prepareXPathExpression(Document document,
            String query) {
        String xpathString = query;
        XPathExpression expression = null;
        try {
            expression = XPATH.compile(xpathString);
        }
        catch (XPathExpressionException e) {
            e.printStackTrace();
        }
        return expression;
    }

    /**
     * The method which serializes a document d
     * @param d
     *            the document to serialize
     * @param fileName
     *            the name of the file
     */
    public static void writeOnDisk(Document d, String fileName) {
        Result result = new StreamResult(new File(fileName));
        Transformer transformer;
        try {
            transformer = TransformerFactory.newInstance().newTransformer();
            transformer.transform(getDOMSource(d), result);
        }
        catch (TransformerConfigurationException e) {
            e.printStackTrace();
        }
        catch (TransformerFactoryConfigurationError e) {
            e.printStackTrace();
        }
        catch (TransformerException e) {
            e.printStackTrace();
        }
    }

    /**
     * Transform a dom tree with an xsl transformation
     * @param d
     *            , the tree to transform
     * @param xslPath
     *            , the xsl transformation
     * @param outName
     *            , the path for the output file
     */
    public static void xml2xsl(Document d, String xslPath, String outName) {
        Source source = getDOMSource(d);
        Source xslSource = new StreamSource(new File(xslPath));
        Result result = new StreamResult(new File(outName));
        Transformer transformer = null;
        try {
            transformer = TransformerFactory.newInstance().newTransformer(
                    xslSource);
            transformer.transform(source, result);
        }
        catch (TransformerConfigurationException e) {
            e.printStackTrace();
        }
        catch (TransformerException e) {
            e.printStackTrace();
        }
    }

    /**
     * Return the result of an xpath evaluation
     * @param document
     *            , the document to parse
     * @param query
     *            , the xpath query
     * @return, the node list for the result
     */
    public static NodeList xPath(Document document, String query) {
        XPathExpression expression = prepareXPathExpression(document, query);
        QName ret = XPathConstants.NODESET;
        NodeList result = null;
        try {
            result = (NodeList) expression.evaluate(document, ret);
        }
        catch (XPathExpressionException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @param document
     *            the document in which the xpath is going to be evaluate
     * @param query
     *            , the query to execute in the document
     * @return a String result using the standard toString() method for a node
     */
    public static String XPath2String(Document document, String query) {
        NodeList nodes = xPath(document, query);
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < nodes.getLength(); i++) {
            buffer.append(nodes.item(i).getTextContent());
            buffer.append(" ");
        }
        return buffer.toString();
    }

    /**
     * Return the result of an xpath expression
     * @param document
     *            , the document to parse
     * @param query
     *            , the query to execute
     * @return a number
     */
    public static Number xPathNumber(Document document, String query) {
        XPathExpression expression = prepareXPathExpression(document, query);
        QName ret = XPathConstants.NUMBER;
        Number result = null;
        try {
            result = (Number) expression.evaluate(document, ret);
        }
        catch (XPathExpressionException e) {
            e.printStackTrace();
        }
        return result;
    }
}
